package com.yeyi.YTool;


import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.WorkbookUtil;

import com.google.common.collect.Lists;

/**
 * @Title: Excel导出工具类, 采用org.apache.poi作为第三方插件进行扩展实现.
 * @Description: 各自导出功能只需要定义一个类, 继承该接口 1.重写好必要的导出文件名, excel表头内容, 导出的表格sheet名称, 输出流, 导出哪个对象的哪些属性(对应表头内容单元格)等方法
 * 2.最后调用父类的export方法即可.
 * @Author: liutang
 * @Created Date: 2016/9/9
 */
public abstract class ExporterAdapter<T> {
	/**
	 * 文件后缀名
	 */
	private static final String SUFFIX = ".xls";
	/**
	 * 工作薄对象
	 */
//	private Workbook workbook;

	/**
	 * 得到具体工作薄对象HSSFWorkbook
	 * <p>
	 *
	 * @return
	 */
	protected Workbook getWorkbook() {
		return new HSSFWorkbook();
	}

	/**
	 * 获取excel表头信息
	 * <p>
	 *
	 * @return excel 表头信息[字符数组]
	 * @since 2013-04-02
	 */
	protected abstract String[] getHeader();

	protected abstract String getBigHeader();

	/**
	 * 获取导出excel的sheet名称,默认不指定(sheet0)
	 * <p>
	 *
	 * @return
	 */
	protected String[] getSheetName() {
		return null;
	}

	/**
	 * 获取导出时需要用到的输出流
	 * <p>
	 *
	 * @return 输出流对象
	 * @throws Exception
	 */
	protected abstract OutputStream getOutputStream() throws Exception;

	/**
	 * 获取数据源中对应索引值
	 * <p>
	 *
	 * @param data 数据源对象
	 * @param index 数据对象中所包含的每个子项目的索引值和导出列的顺序一致
	 * @return
	 */
	protected abstract String getCellValue(T data, int index);

	/**
	 * 创建数据行对象,设置行中的每个单元格样式、值
	 * <p>
	 *
	 * @param sheet 表单对象
	 * @param startRow 行标
	 * @param columnIndex 列标
	 * @param cellStyle 单元格样式
	 * @param data 数据源
	 */
	protected void createRow(Sheet sheet, int startRow, int columnIndex, CellStyle cellStyle, T data) {
		/**
		 * 利用表单,行索引,创建一个Row对象
		 */
		Row row = sheet.createRow(startRow);
		for (int i = 0; i < columnIndex; i++) {
			// 创建一个单元格
			Cell cell = row.createCell(i);
			cell.setCellValue(this.getCellValue(data, i));
			cell.setCellStyle(cellStyle);
		}
	}

	/**
	 * 创建表头行对象,对表头行的单元格分配具体表头内容
	 * <p>
	 *
	 * @param sheet 表单对象
	 * @param startRow 行标
	 * @param columnIndex 列标
	 * @param cellStyle 单元格样式
	 * @param data 数据源
	 * @param mergeArray 需要合并的单元格
	 */
	protected void createHeaderRow(Sheet sheet, int startRow, int columnIndex, CellStyle cellStyle, Object[] data,
			int[][] mergeArray) {
		if (mergeArray != null && mergeArray.length > 0) {
			for (int i = 0; i < mergeArray.length; i++) {
				sheet.addMergedRegion(new CellRangeAddress(mergeArray[i][0], mergeArray[i][1], mergeArray[i][2],
						mergeArray[i][3]));
			}
		}

		// 若为二维数组
		if (data != null && data[0].getClass().isArray()) {
			for (int j = 0; j < data.length; j++) {
				String[] strings = (String[]) data[j];
				// 创建一行
				Row row = sheet.createRow(startRow + j);
				for (int k = 0; k < strings.length; k++) {
					Cell cell = row.createCell(k);// 创建一个单元格
					cell.setCellValue(strings[k]);// 设置单元格值
					cell.setCellStyle(cellStyle);// 设置单元格样式
				}
			}
		} else {
			// 若为一维数组
			Row row = sheet.createRow(startRow);// 创建一行
			for (int i = 0; i < columnIndex; i++) {
				Cell cell = row.createCell(i);// 创建一个单元格
				cell.setCellValue((String) data[i]);// 设置单元格值
				cell.setCellStyle(cellStyle);// 设置单元格样式
			}
		}
	}

	/**
	 * 获取单元格默认样式,主要对单元格cell对象设置了一些展示的样式
	 * <p>
	 *
	 * @param workbook 工作薄对象
	 * @return 单元格样式对象
	 */
	protected CellStyle getDefaultCellStyle(Workbook workbook) {
		// 创建一个单元格样式对象
		CellStyle cellStyle = workbook.createCellStyle();
		cellStyle.setAlignment(CellStyle.ALIGN_CENTER);// 对齐方式:居中

		/**
		 * 设置边框样式:无边框样式
		 */
		cellStyle.setBorderBottom(CellStyle.BORDER_NONE);
		cellStyle.setBorderTop(CellStyle.BORDER_NONE);
		cellStyle.setBorderLeft(CellStyle.BORDER_NONE);
		cellStyle.setBorderRight(CellStyle.BORDER_NONE);
		return cellStyle;
	}

	/**
	 * 导出excel执行总方法
	 * <p>
	 *
	 * @param workbook 工作薄对象
	 * @param headers excel表头
	 * @param cellStyle 单元格样式
	 * @param data 需要导出的数据源(对象集合)
	 * @param sheetName sheet表单名称
	 */
	protected void execute(Workbook workbook, String[] headers, CellStyle cellStyle, List<T> data, String sheetName) {
		this.execute(workbook, new String[][] { headers }, null, cellStyle, data, sheetName);
	}

	protected void execute1(Workbook workbook, String[] headers, String bigHeaders, String operationName,
			CellStyle cellStyle, List<T> data, String sheetName) {
		this.execute1(workbook, new String[][] { headers }, bigHeaders, operationName, null, cellStyle, data, sheetName);
	}

	protected void execute2(Workbook workbook, String[] headers, String bigHeaders, String operationName,
			CellStyle cellStyle, List<T> data, String sheetName) {
		this.execute2(workbook, new String[][] { headers }, bigHeaders, operationName, null, cellStyle, data, sheetName);
	}

	/**
	 * 导出excel执行总方法
	 * <p>
	 *
	 * @param workbook 工作薄对象
	 * @param headers excel表头
	 * @param mergeArray 合并单元格数
	 * @param cellStyle 单元格样式
	 * @param data 需要导出的数据源(对象集合)
	 * @param sheetName sheet表单名称
	 */
	protected void execute(Workbook workbook, String[][] headers, int[][] mergeArray, CellStyle cellStyle,
			List<T> data, String sheetName) {
		/**
		 * 总行数
		 */
		int totalRows = data.size();

		int startRow = 0;// 表单体开始行
		int oneIndex = headers[0].length;

		int colsIndex = 0;// 列数

		if (mergeArray == null || mergeArray.length == 0) {
			colsIndex = headers.length;
			startRow = 1;
		} else {
			colsIndex = headers[0].length;
			startRow = headers.length;
		}

		/**
		 * 创建一个表单对象
		 */
		Sheet sheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(sheetName));
		/**
		 * 根据表单对象,创建表单头信息内容
		 */
		this.createHeaderRow(sheet, 0, colsIndex, cellStyle, headers, mergeArray);

		for (int rowIndex = 0; rowIndex < totalRows; rowIndex++) {
			/**
			 * 根据表单对象,创建表单的所有行
			 */
			this.createRow(sheet, rowIndex + startRow, oneIndex, cellStyle, data.get(rowIndex));
		}
	}

	protected void execute1(Workbook workbook, String[][] headers, String bigHeader, String operationName,
			int[][] mergeArray, CellStyle cellStyle, List<T> data, String sheetName) {
		/**
		 * 总行数
		 */
		int totalRows = data.size();

		int startRow = 0;// 表单体开始行
		int oneIndex = headers[0].length;

		int colsIndex = 0;// 列数

		if (mergeArray == null || mergeArray.length == 0) {
			colsIndex = headers.length + 2;
			startRow = 1 + 2;
		} else {
			colsIndex = headers[0].length + 2;
			startRow = headers.length + 2;
		}

		/**
		 * 创建一个表单对象
		 */
		Sheet sheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(sheetName));
		// 插入大标题
		String[] bh = { bigHeader };
		int[][] bhi = { { 0, 0, 0, oneIndex } };
		this.createHeaderRow(sheet, 0, 1, cellStyle, bh, bhi);
		// 当前时间和导入出人名称
		int[][] opName = { { 1, 1, 0, 1 }, { 1, 1, oneIndex - 2, oneIndex } };
		String[] opNameIndex = new String[oneIndex - 3];
		for (int i = 0; i < opNameIndex.length; i++) {
			if (i == 0) {
				opNameIndex[i] = "导出时间：" + new Date().toLocaleString();
			} else if (i == opNameIndex.length - 1) {
				opNameIndex[i] = "导出人姓名：" + operationName;
			} else {
				opNameIndex[i] = "";
			}
		}
		this.createHeaderRow(sheet, 1, oneIndex - 3, cellStyle, opNameIndex, opName);
		/**
		 * 根据表单对象,创建表单头信息内容
		 */
		this.createHeaderRow(sheet, 2, colsIndex, cellStyle, headers, mergeArray);

		for (int rowIndex = 0; rowIndex < totalRows; rowIndex++) {
			/**
			 * 根据表单对象,创建表单的所有行
			 */
			this.createRow(sheet, rowIndex + startRow, oneIndex, cellStyle, data.get(rowIndex));
		}
	}

	protected void execute2(Workbook workbook, String[][] headers, String bigHeader, String operationName,
			int[][] mergeArray, CellStyle cellStyle, List<T> data, String sheetName) {
		/**
		 * 总行数
		 */
		int totalRows = data.size();

		int startRow = 0;// 表单体开始行
		int oneIndex = headers[0].length;

		int colsIndex = 0;// 列数

		if (mergeArray == null || mergeArray.length == 0) {
			colsIndex = headers.length + 2;
			startRow = 1 + 2;
		} else {
			colsIndex = headers[0].length + 2;
			startRow = headers.length + 2;
		}

		/**
		 * 创建一个表单对象
		 */
		Sheet sheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(sheetName));
		// 插入大标题
		String[] bh = { bigHeader };
		int[][] bhi = { { 0, 0, 0, oneIndex } };
		this.createHeaderRow(sheet, 0, 1, cellStyle, bh, bhi);
		// 当前时间和导入出人名称
		int[][] opName = { { 1, 1, 0, 1 }, { 1, 1, oneIndex - 2, oneIndex } };
		String[] opNameIndex = new String[oneIndex - 3];
		for (int i = 0; i < opNameIndex.length; i++) {
			if (i == 0) {
				opNameIndex[i] = "导出时间：" + new Date().toLocaleString();
			} else if (i == opNameIndex.length - 1) {
				opNameIndex[i] = "导出人姓名：" + operationName;
			} else {
				opNameIndex[i] = "";
			}
		}
		this.createHeaderRow(sheet, 1, oneIndex - 3, cellStyle, opNameIndex, opName);
		/**
		 * 根据表单对象,创建表单头信息内容
		 */
		this.createHeaderRow(sheet, 2, colsIndex, cellStyle, headers, mergeArray);

		for (int rowIndex = 0; rowIndex < totalRows; rowIndex++) {
			/**
			 * 根据表单对象,创建表单的所有行
			 */
			this.createRow(sheet, rowIndex + startRow, oneIndex, cellStyle, data.get(rowIndex));
		}
	}

	/**
	 * 执行导出操作
	 * <p>
	 *
	 * @param data
	 * @throws Exception
	 */
	protected void export(List<T> data) throws Exception {
		/**
		 * 无数据,直接返回,不进行任何操作
		 */
		// if (data == null || data.size() == 0) {
		// return;
		// }

		/**
		 * 表单头内容信息
		 */
		String[] headers = this.getHeader();

		/**
		 * 总行数
		 */
		int totalRows = data.size();

		/**
		 * 总列数
		 */
		int totalCols = headers.length;

		// 工作薄对象
		Workbook workbook = this.getWorkbook();

		// 得到需要设置的单元格样式对象
		CellStyle cellStyle = this.getDefaultCellStyle(workbook);

		// 表单名称
		String[] sheetNames = this.getSheetName();

		// 输出流对象
		OutputStream outputStream = this.getOutputStream();

		// 未指定sheet名称时,按默认的sheet名称写数据内容到sheet表单
		if (sheetNames == null) {
			Sheet sheet = workbook.createSheet();
			// 创建表格头内容信息到各个单元格
			this.createHeaderRow(sheet, 0, totalCols, cellStyle, headers, null);

			// 创建表格所有行
			for (int rowIndex = 0; rowIndex < totalRows; rowIndex++) {
				// 从第二行开始创建(第一行是表头信息内容)
				this.createRow(sheet, rowIndex + 1, totalCols, cellStyle, data.get(rowIndex));
			}
		} else {
			// 按照sheet写入数据
			for (int i = 0; i < sheetNames.length; i++) {
				this.execute(workbook, headers, cellStyle, data, sheetNames[i]);
			}
		}

		/**
		 * 将当前工作薄写入数据内容信息
		 */
		workbook.write(outputStream);
		outputStream.flush();
		outputStream.close();// 关闭输出流
	}

	protected void export1(List<T> data, String operationName) throws Exception {
		/**
		 * 无数据,直接返回,不进行任何操作
		 */
		if (data == null || data.size() == 0) {
			return;
		}

		/**
		 * 表单头内容信息
		 */
		String[] headers = this.getHeader();

		String bigHeaders = this.getBigHeader();

		/**
		 * 总行数
		 */
		int totalRows = data.size();

		/**
		 * 总列数
		 */
		int totalCols = headers.length;

		// 工作薄对象
		Workbook workbook = this.getWorkbook();

		// 得到需要设置的单元格样式对象
		CellStyle cellStyle = this.getDefaultCellStyle(workbook);

		// 表单名称
		String[] sheetNames = this.getSheetName();

		// 输出流对象
		OutputStream outputStream = this.getOutputStream();

		// 未指定sheet名称时,按默认的sheet名称写数据内容到sheet表单
		if (sheetNames == null) {
			Sheet sheet = workbook.createSheet();
			// 创建表格头内容信息到各个单元格
			this.createHeaderRow(sheet, 0, totalCols, cellStyle, headers, null);

			// 创建表格所有行
			for (int rowIndex = 0; rowIndex < totalRows; rowIndex++) {
				// 从第二行开始创建(第一行是表头信息内容)
				this.createRow(sheet, rowIndex + 1, totalCols, cellStyle, data.get(rowIndex));
			}
		} else {
			// 按照sheet写入数据
			for (int i = 0; i < sheetNames.length; i++) {
				this.execute1(workbook, headers, bigHeaders, operationName, cellStyle, data, sheetNames[i]);
			}
		}

		/**
		 * 将当前工作薄写入数据内容信息
		 */
		workbook.write(outputStream);
		outputStream.flush();
		outputStream.close();// 关闭输出流
	}

	protected void export2(List<T> data, String operationName) throws Exception {
		/**
		 * 无数据,直接返回,不进行任何操作
		 */
		if (data == null || data.size() == 0) {
			return;
		}

		/**
		 * 表单头内容信息
		 */
		String[] headers = this.getHeader();

		String bigHeaders = this.getBigHeader();

		/**
		 * 总行数
		 */
		int totalRows = data.size();

		/**
		 * 总列数
		 */
		int totalCols = headers.length;

		// 工作薄对象
		Workbook workbook = this.getWorkbook();

		// 得到需要设置的单元格样式对象
		CellStyle cellStyle = this.getDefaultCellStyle(workbook);

		// 表单名称
		String[] sheetNames = this.getSheetName();

		// 输出流对象
		OutputStream outputStream = this.getOutputStream();

		// 未指定sheet名称时,按默认的sheet名称写数据内容到sheet表单
		if (sheetNames == null) {
			Sheet sheet = workbook.createSheet();
			// 创建表格头内容信息到各个单元格
			this.createHeaderRow(sheet, 0, totalCols, cellStyle, headers, null);

			// 创建表格所有行
			for (int rowIndex = 0; rowIndex < totalRows; rowIndex++) {
				// 从第二行开始创建(第一行是表头信息内容)
				this.createRow(sheet, rowIndex + 1, totalCols, cellStyle, data.get(rowIndex));
			}
		} else {
			int limit = 60000;
			if (data.size() > limit) {
				int page = data.size() % limit == 0 ? data.size() / limit : data.size() / limit + 1;

				for (int i = 0; i < page; i++) {
					List<T> part = Lists.newArrayList();
					if (i == page - 1) {
						for (int k = i * limit; k < data.size(); k++) {
							part.add(data.get(k));
						}
					} else {
						for (int j = i * limit; j < (i * limit == 0 ? limit : (i + 1) * limit); j++) {
							part.add(data.get(j));
						}
					}
					// 按照sheet写入数据
					this.execute2(workbook, headers, bigHeaders, operationName, cellStyle, part, sheetNames[0] + i);

				}
			} else {
				this.execute2(workbook, headers, bigHeaders, operationName, cellStyle, data, sheetNames[0]);
			}

		}

		/**
		 * 将当前工作薄写入数据内容信息
		 */
		workbook.write(outputStream);
		outputStream.flush();
		outputStream.close();// 关闭输出流
	}

	/**
	 * 默认的导出文件名称
	 *
	 * @return 文件名称
	 */
	public String getFileName() {
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyMMdd");
		String fileName = simpleDateFormat.format(new Date());
		// 文件名为“商户列表XX（年份后2位）XX（月份）XX（日期）”
		return "商户列表"+fileName+SUFFIX;
	}
}